#include <iostream>
#include <mutex>
#include <thread>
#include <vector>
#include <queue>
#include <condition_variable>
#include <atomic>
#include <functional>
#include <future>

using task_t=std::function<void()>;

class ThreadPool
{
private:
    std::vector<std::thread> workers;
    std::queue<task_t> tasks;
    std::mutex queueMutex;
    std::condition_variable cv;
    std::atomic<bool> stop;
private:
    ThreadPool(size_t threadNums) : stop(false)
    {
        for(int i=0;i<threadNums;i++)
        {
            workers.emplace_back([this]()
            {
                while(true)
                {
                    task_t task;
                    {
                        std::unique_lock<std::mutex> lock(queueMutex);
                        this->cv.wait(lock,[this](){return this->stop||!tasks.empty();});
                        if(this->stop && this->tasks.empty()) break;
                        task=std::move(this->tasks.front());
                        this->tasks.pop();
                    }
                    task();
                }
            });
        }
    }
public:
    static ThreadPool* GetInstance(size_t threadNums=4)
    {
        static ThreadPool _pool(threadNums);
        return &_pool;
    }
    ThreadPool(ThreadPool const&)=delete;
    ThreadPool& operator=(ThreadPool const&)=delete;

    template<class F,class ...Args>
    auto enqueue(F&& f,Args... args)
    {
        using returnType=typename std::result_of<F(Args...)>::type;
        auto task=std::make_shared<std::packaged_task<returnType()>>(std::bind(std::forward<F>(f),std::forward<Args>(args)...));
        std::future<returnType> res=task->get_future();
        {
            std::unique_lock<std::mutex> lock(queueMutex);
            tasks.emplace([task](){(*task)()});
        }
        cv.notify_one();
        return res;
    }
};